package org.bukkit.pm.dmatch;

import it.patamau.bukkit.GenericPlugin;
import it.patamau.bukkit.MySQLConnection;
import it.patamau.bukkit.PluginShutdownHook;

import java.util.Arrays;
import java.util.HashMap;
import java.util.logging.Level;

import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.Event.Priority;
import org.bukkit.plugin.PluginManager;

/**
 * DM manager for Bukkit
 *
 * @author patamau
 */
public class DMatchPlugin extends GenericPlugin {
	
	public static final long LOOPTHREADTO = 1000; //check loop conditions every second
	
	public static final String
		KEY_JHELP="dm.join.help",
		KEY_JINFO="dm.join.info",
		KEY_JSTAT="dm.join.stat",
		KEY_JLIST="dm.join.list",
		KEY_MHEAD="dm.message.header",
		KEY_STARTED="dm.started",
		KEY_PAUSED="dm.paused",
		KEY_BROADCAST="dm.broadcast",
		KEY_ENV="dm.environment",
		KEY_HMTO="dm.hmtimeout",
		KEY_DTO="dm.dtimeout",
		KEY_LIST="dm.list",
		KEY_LOOPWAIT="dm.loop.wait",
		KEY_LOOPTIME="dm.loop.time",
		KEY_LOOPKILLS="dm.loop.kills",
		KEY_PRIZES="dm.prizes",
		KEY_MSURL="dm.mysql.url",
		KEY_MSUSER="dm.mysql.user",
		KEY_MSPASS="dm.mysql.pass",
		KEY_LISTSIZE="dm.listsize";
	
	public static final boolean
		DEF_MHEAD=false,
		DEF_STARTED=false,
		DEF_BROADCAST=false,
		DEF_ENV=true;
	public static final boolean
		DEF_PAUSED=false,
		DEF_JLIST=true,
		DEF_JHELP=false,
		DEF_JINFO=false,
		DEF_JSTAT=true;
	public static final String
		DEF_MSURL = "jdbc:mysql://localhost:3306/mc",
		DEF_MSUSER = "",
		DEF_MSPASS = "",
		DEF_LIST="";
	public static final int
		DEF_LOOPWAIT=5,
		DEF_LOOPTIME=600,
		DEF_LOOPKILLS=20,
		DEF_LISTSIZE=3;
	public static final long
		DEF_HMTO=100,
		DEF_DTO=5000;
	
	public static final String
		LIST_SPLIT = ";",
		LIST_SEP = ":";
	
    private final DMatchEntityListener _entityListener = new DMatchEntityListener(this);
    private final DMatchPlayerListener _playerListener = new DMatchPlayerListener(this);
    //private final DMatchServerListener _serverListener = new DMatchServerListener(this);
    private final HashMap<String, DMatchPlayer> _players = new HashMap<String, DMatchPlayer>();
    private final MySQLConnection _dbconn = new MySQLConnection();
    private final LoopThread _loopThread = new LoopThread();
    
    private boolean 
    	_mhead = DEF_MHEAD,
    	_started = DEF_STARTED, 
    	_paused = DEF_PAUSED,
    	_broadcast = DEF_BROADCAST, 
    	_environment = DEF_ENV;
    private long 
    	_loopwait = DEF_LOOPWAIT,
    	_hmtimeout = DEF_HMTO, 
    	_dtimeout = DEF_DTO;
    private int 
    	_maxkills = 0, //store max number of kills
    	_loopstart = -1, //loop start time
    	_loopkills = DEF_LOOPKILLS,
    	_looptime = DEF_LOOPTIME,
    	_listsize = DEF_LISTSIZE;
    private boolean
    	_jlist = DEF_JLIST,
    	_jhelp = DEF_JHELP,
    	_jinfo = DEF_JINFO,
    	_jstat = DEF_JSTAT;
    
    //used internally to disable the plugin only once
    private boolean _disabled;
    
    private class LoopThread extends Thread{
    	public void run(){
    		while(!_disabled){
	    		do{ //loop check
		    		synchronized(_loopThread){
		    			try {
							_loopThread.wait(LOOPTHREADTO);
						} catch (InterruptedException e) {
							continue; //it's fine, checkLoop will verify interruption
						}
		    		}
	    		}while(checkLoop());
	    		if(!_disabled){
	    			//TODO: apply loop timeout
	    		}
    		}//disabled (quit)
    	}
    }
    
    public DMatchPlugin() {
        super();
        //register shutdown action
        new PluginShutdownHook(this);
    }

    public void onEnable() {
    	_disabled=false;
    	//generic plugin will load properties and initialize version number and name
    	init();
    	
    	//load parameters
    	_mhead = getProperty(KEY_MHEAD,DEF_MHEAD);
    	_started = getProperty(KEY_STARTED,DEF_STARTED);
    	_broadcast = getProperty(KEY_BROADCAST,DEF_BROADCAST);
    	_environment = getProperty(KEY_ENV,DEF_ENV);
    	_hmtimeout = getProperty(KEY_HMTO,DEF_HMTO);
    	_dtimeout = getProperty(KEY_DTO,DEF_DTO);
    	_listsize = getProperty(KEY_LISTSIZE,DEF_LISTSIZE);
    	_jhelp = getProperty(KEY_JHELP,DEF_JHELP);
    	_jinfo = getProperty(KEY_JINFO,DEF_JINFO);
    	_jstat = getProperty(KEY_JSTAT,DEF_JSTAT);
    	_jlist = getProperty(KEY_JLIST,DEF_JLIST);
    	//load player list
    	String list = getProperty(KEY_LIST,DEF_LIST);
    	for(String s: list.split(LIST_SPLIT)){
    		DMatchPlayer dp = DMatchPlayer.parse(s, this);
    		if(dp!=null) _players.put(dp.getName(), dp);
    	}
    	//try mysql connection
    	if(hasProperty(KEY_MSURL)){
    		String url = getProperty(KEY_MSURL,DEF_MSURL);
    		String user = getProperty(KEY_MSUSER,DEF_MSUSER);
    		String password = getProperty(KEY_MSPASS,DEF_MSPASS);
    		_dbconn.connect(url, user, password);
    	}
    	
        // register events
        PluginManager pm = getServer().getPluginManager();
        pm.registerEvent(Event.Type.PLAYER_COMMAND_PREPROCESS, _playerListener, Priority.Normal, this);
        pm.registerEvent(Event.Type.PLAYER_QUIT, _playerListener, Priority.Normal, this);
        pm.registerEvent(Event.Type.PLAYER_JOIN, _playerListener, Priority.Normal, this);
        
        pm.registerEvent(Event.Type.ENTITY_DAMAGED, _entityListener, Priority.Normal, this);
        pm.registerEvent(Event.Type.ENTITY_COMBUST, _entityListener, Priority.Normal, this);
        pm.registerEvent(Event.Type.ENTITY_DEATH, _entityListener, Priority.Normal, this);
        
        //pm.registerEvent(Event.Type.SERVER_COMMAND, _serverListener, Priority.Normal, this);
        
        //start loop thread
        _loopThread.start();
    }
    
    //--- getters and setters ---
   
    public void setLoopWait(int timeout){
    	if(setProperty(KEY_LOOPWAIT,timeout)){
    		_loopwait=timeout;
    	}
    }
    
    public long getLoopWait(){
    	return _loopwait;
    }
    
    public void setLoopTime(int limit){
    	if(setProperty(KEY_LOOPTIME,limit)){
    		_looptime=limit;
    		notifyLoop();
    	}
    }
    
    public int getLoopTime(){
    	return _looptime;
    }
    
    public void setLoopKills(int limit){
    	if(setProperty(KEY_LOOPKILLS,limit)){
    		_loopkills=limit;
    		notifyLoop();
    	}
    }
    
    public int getLoopKills(){
    	return _loopkills;
    }
    
    public boolean hasJoinList(){
    	return _jlist;
    }
    
    public void setJoinList(boolean jlist){
    	if(setProperty(KEY_JLIST,jlist)){
    		_jlist=jlist;
    	}
    }
    
    public boolean hasMessageHeader(){
    	return _mhead;
    }
    
    public void setMessageHeader(boolean mhead){
    	if(setProperty(KEY_MHEAD,mhead)){
    		_mhead=mhead;
    	}
    }
    
    public boolean hasJoinHelp(){
    	return _jhelp;
    }
    
    public void setJoinHelp(boolean jhelp){
    	if(setProperty(KEY_JHELP,jhelp)){
    		_jhelp=jhelp;
    	}
    }
    
    public boolean hasJoinInfo(){
    	return _jinfo;
    }
    
    public void setJoinInfo(boolean jinfo){
    	if(setProperty(KEY_JINFO,jinfo)){
    		_jinfo=jinfo;
    	}
    }
    
    public boolean hasJoinStat(){
    	return _jstat;
    }
    
    public void setJoinStat(boolean jstat){
    	if(setProperty(KEY_JSTAT,jstat)){
    		_jstat=jstat;
    	}
    }
    
    void setStarted(boolean start){
    	//force unpause
    	setPaused(false);
    	//only restart if it was stopped
    	if(setProperty(KEY_STARTED,start)){
    		_started=start;
    		if(_started){
    			//clear old stats before starting again
    			reset();
    			broadcast("Deathmatch started!",null,null);
    		}else{ 
    			broadcast("Deathmatch finished!",null,null);
    			//check for loop settings
    			//TODO: loop.timeout
    		}
    	}
    }
    
    public void start(){
    	setStarted(true);
    }
    
    public void stop(){
    	setStarted(false);
    }
    
    public boolean isStarted(){
    	return _started;
    }
    
    public void setPaused(boolean pause){
    	if(setProperty(KEY_PAUSED,pause)){
    		_paused=pause;
    		if(_paused) broadcast("Deathmatch paused!",null,null);
    		else broadcast("Deathmatch resumed!",null,null);
    	}
    }
    
    public boolean isPaused(){
    	return _paused;
    }
    
    public void setBroadcast(boolean broadcast){
    	if(setProperty(KEY_BROADCAST,broadcast)){
    		_broadcast=broadcast;
    	}
    }
    
    public boolean getBroadcast(){
    	return _broadcast;
    }
    
    public void setEnvironment(boolean environment){
    	if(setProperty(KEY_ENV,environment)){
    		_environment=environment;
    		if(_environment) broadcast("Counting environment deaths!",null,null);
    		else broadcast("Counting PvP only!", null,null);
    	}
    }
    
    public boolean getEnvironment(){
    	return _environment;
    }
    
    public void setHMTimeout(long hmto){
    	if(setProperty(KEY_HMTO,hmto)){
    		_hmtimeout=hmto;
    	}
    }
    
    public long getHMTimeout(){
    	return _hmtimeout;
    }
    
    public void setDTimeout(long dto){
    	if(setProperty(KEY_DTO,dto)){
    		_dtimeout=dto;
    	}
    }
    
    public long getDTimeout(){
    	return _dtimeout;
    }
    
    public void setListSize(int size){
    	if(size<=0) size=1; //force list size to at least 1 player
    	if(setProperty(KEY_LISTSIZE,size)){
    		_listsize=size;
    	}
    }
    
    public int getListSize(){
    	return _listsize;
    }
    
    public MySQLConnection getConnection(){
    	return _dbconn;
    }
    
    //--- end: getters and setters ---
    
    public DMatchPlayer[] getDMPlayers(){
    	DMatchPlayer[] pps = _players.values().toArray(new DMatchPlayer[_players.size()]);
    	Arrays.sort(pps);
    	return pps;
    }
    
    /**
     * Retrieve the DMPlayer associated with the given name
     * If no such player exists, null is returned
     * @param name
     * @return
     */
    public DMatchPlayer findDMPlayer(String name){
    	return _players.get(name);
    }
    
    /**
     * Retrieve a DMatchPlayer from the local cache
     * If no DMatchPlayer exists with that name, a new one is created and added to the list
     * @param name
     * @return
     */
    public DMatchPlayer getDMPlayer(String name){
    	if(name==null) return null;
    	DMatchPlayer pp = _players.get(name);
    	if(pp==null){
    		pp = new DMatchPlayer(name, 0, 0, this);
    		_players.put(name, pp);
    		log(Level.FINE,"player "+name+" ranking initialied");
    	}
    	return pp;
    }
    
    /**
     * Verify the victim is dead
     * killed by the (optional) killer
     * This will notify the health check thread 
     * and eventually trigger the death status in the DMPlayer
     * @param victim
     * @param killer
     */
    public void checkDead(String victim, String killer){
    	//prevent recording stats when no DM is enabled
    	if(!isStarted()||isPaused()) return;
    	//check environment flag
    	if(killer==null&&!getProperty(KEY_ENV,DEF_ENV)) return;
    	DMatchPlayer dp = getDMPlayer(victim); //provide DMPlayer instance
    	dp.checkHealth(getDMPlayer(killer)); //trigger health monitor thread
    }
    
    /**
     * Notify the loop thread that something changed
     * and a check is needed
     */
    public void notifyLoop(){
    	synchronized(_loopThread){
    		_loopThread.notify();
    	}
    }
    
    /**
     * Verify loop conditions are met
     * @return true if the loop can continue, false if it has to be stopped
     */
    public boolean checkLoop(){
    	if(_disabled) return false; //kill thread when leaving
    	
    	if(_looptime>0){
    		long dt = System.currentTimeMillis()-_loopstart;
    		if(dt>=_looptime){
    			stop();
    			return false;
    		}
    	}
    	//TODO: check loop kills
    	return true;
    }
    
    /**
     * Removes all rankings
     */
    public void reset(){
    	//remove all recorded players
    	_players.clear();
    	broadcast("rankings cleared!",null,null);
    	//reinitialize online players
    	for(Player p: getServer().getOnlinePlayers()){
    		getDMPlayer(p.getName());
    	}
    }
   
	/**
	 * Used to send a message with a standard plugin prefix
	 * @param player
	 * @param message
	 */
	public void sendDMMessage(Player player, String message){
		if(player==null) return; //XXX
		StringBuffer sb = new StringBuffer();
		if(hasMessageHeader()){
			sb.append(ChatColor.DARK_RED);
			sb.append("[DM] ");
		}
		sb.append(ChatColor.RED);
		sb.append(message);
		player.sendMessage(sb.toString());
	}
	
    public void printStatus(Player p){
    	StringBuffer sb = new StringBuffer();
		sb.append(ChatColor.DARK_RED);
		sb.append("DeathMatch ");
		sb.append(ChatColor.RED);
		sb.append("is currently ");
		sb.append(ChatColor.YELLOW);
       	//print the dm status (active or not)
    	if(isStarted()){    
    		if(isPaused()){
    			sb.append("PAUSED");
    		}else{
    			sb.append("ON");
    		}
    		if(!getEnvironment()){
        		sb.append(ChatColor.RED);
    			sb.append(" (PvP only)");
    		}
    	}else{
    		sb.append("OFF");
    	}
    	p.sendMessage(sb.toString());
    }
	
	public void printInfo(Player player){
		StringBuffer sb = new StringBuffer();
		sb.append(ChatColor.DARK_RED);
		sb.append(getName());
		sb.append(" ");
		sb.append(getVersion());
		player.sendMessage(sb.toString());
		for(Object k: _properties.keySet()){
			if(((String)k).equalsIgnoreCase(KEY_LIST)) continue;
			String s = (String) _properties.get(k);
			sb.setLength(0);
			sb.append(ChatColor.RED);
			sb.append(k);
			sb.append(" ");
			sb.append(ChatColor.YELLOW);
			sb.append(s);
			player.sendMessage(sb.toString());
		}
	}
    
    private void printDMPlayerEntry(int i, DMatchPlayer d, Player p){
    	String is = DMatchPlayer.normalize(Integer.toString(i+1), Integer.toString(getProperty(KEY_LISTSIZE,DEF_LISTSIZE)).length());
		p.sendMessage(ChatColor.YELLOW+is+" "+d.toString());
    }
    
    public void printRank(String name, Player p){
    	DMatchPlayer[] list = getDMPlayers();
    	for(int i=0; i<list.length; i++){
    		if(list[i].getName().equals(name)){
    			sendDMMessage(p,ChatColor.WHITE+name+ChatColor.RED+" rank:");
    			printDMPlayerEntry(i, list[i],p);
    			return;
    		}
    	}
    	sendDMMessage(p," No such player ranked");
    }
    
    public void printList(Player p){
    	StringBuffer sb = new StringBuffer();
		if(hasMessageHeader()){
			sb.append(ChatColor.DARK_RED);
			sb.append("[DM] ");
		}
    	if(_players.size()==0){
    		sb.append(ChatColor.RED);
    		sb.append("No players recorded (yet)");
    		p.sendMessage(sb.toString());
    	}else{
        	DMatchPlayer[] ps = getDMPlayers();
        	sb.append(ChatColor.RED);
        	sb.append("Rankings:");
    		p.sendMessage(sb.toString());
    		int i=0, limit = getProperty(KEY_LISTSIZE,DEF_LISTSIZE);
            for(DMatchPlayer pp: ps){
            	printDMPlayerEntry(i,pp,p);
            	if(i++>limit) break; //used to limit rankings
            }
    	}
    }

    /**
     * Used to broadcast messages to all the connected players
     * but the given players (usually victim and killer)
     * @param message
     */
    public void broadcast(String message, String a, String b){
    	StringBuffer sb = new StringBuffer();
    	if(hasMessageHeader()) {
    		sb.append(ChatColor.DARK_RED);
    		sb.append("[DM] ");
    	}
    	sb.append(ChatColor.RED);
    	sb.append(message);
    	message = sb.toString();
    	for(Player p: this.getServer().getOnlinePlayers()){
    		if(a!=null&&a.equals(p.getName())) continue; //skip this player
    		if(b!=null&&b.equals(p.getName())) continue; //skip this player too
    		p.sendMessage(message);
    	}
    }
    
    /**
     * Store properties
     */
    public synchronized void onDisable() {
    	if(_disabled) return; //avoid double disable
    	_disabled=true;
		StringBuffer sb = new StringBuffer();
    	for(DMatchPlayer p: _players.values()){
    		sb.append(p.serialize());
    		sb.append(DMatchPlugin.LIST_SPLIT);
    		p.stopHMonitor(); //force stop thread
    	}
    	//save property with seriealized form of players
    	setProperty(KEY_LIST,sb.toString());
    	//clear memory
    	_players.clear();
    	//call generic plugin close method
		close();
    }
}
